/**
 * 
 */
package components;

import components.exceptions.BinaryToIntException;
import components.exceptions.IntToBinaryException;
import components.exceptions.SubArrayException;

/**
 * 
 * Utility Class.
 * 
 * @author Chz
 * 
 */
public class Util {

	private Util() {

	}

	/***
	 * convert integer into binary, the result is an int[binaryBit] consists of
	 * 1s and 0s. By default, the sigbit switch is turned off.
	 * 
	 * @param integer
	 * @param binaryBitNum
	 * @return
	 * @throws IntToBinaryException
	 */
	public static int[] intToBinary(int integer, int binaryBitNum)
			throws IntToBinaryException {
		return intToBinary(integer, binaryBitNum, false);
	}

	public static int[] intToBinary(int integer, int binaryBitNum,
			boolean sigbit) throws IntToBinaryException {
		int i = 0;
		int[] ret = new int[binaryBitNum];
		char[] tmp;
		int range;
		if (sigbit)
			range = (int) Math.pow(2, binaryBitNum - 1);
		else
			range = (int) Math.pow(2, binaryBitNum);

		int lowerRange = -range;
		int upperRange = range - 1;

		// make sure the content can be fill into a work without loss.
		if (integer > upperRange || integer < lowerRange) {
			String infoStr = integer + ": exceeded the range from "
					+ lowerRange + " to " + upperRange + ","
					+ " cannot be represented in ";
			if (sigbit)
				infoStr += binaryBitNum - 1;
			else
				infoStr += binaryBitNum;

			infoStr += " bits.";

			// throw new IntToBinaryException(infoStr);
			System.err.println(infoStr);
		}

		tmp = Integer.toBinaryString(integer).toCharArray();

		// if it's a negative number, the tmp's length will
		// exceeded 16 bits into 32 bits, so the for loop must be stop when i
		// reaches 16.
		// System.err.println(Integer.toBinaryString(integer));

		for (; i < Math.min(tmp.length, binaryBitNum); ++i) {
			if (tmp[tmp.length - i - 1] == '1')
				ret[binaryBitNum - i - 1] = 1;
			else
				ret[binaryBitNum - i - 1] = 0;
		}
		for (; i < binaryBitNum - 1; ++i)
			ret[binaryBitNum - i - 1] = 0;

		if (sigbit)
			if (integer >= 0)
				ret[0] = 0; // i is positive, sig bit = 0
			else
				ret[0] = 1; // i is positive, sig bit = 0

		return ret;

	}

	/*************************************
	 * transform binary array into integer*
	 *************************************/

	/***
	 * Calling this method without the boolean signal parameter means
	 * translation without sig bit.
	 * 
	 * @param parm
	 * @return
	 * @throws BinaryToIntException
	 */
	public static int binaryToInt(int[] parm) throws BinaryToIntException {
		return binaryToInt(parm, false);
	}

	/***
	 * translate the binary array into integer. When the signal is set, then the
	 * function exam the length of the array. Only if the length is 8 or 16 or
	 * 32 or 64, then consider the first bit as signal bit. If the array is of
	 * other length, then treat it as a non-sig array.
	 * 
	 * @param parm
	 * @return
	 * @throws BinaryToIntException
	 */
	public static int binaryToInt(int[] parm, boolean signal)
			throws BinaryToIntException {

		int sigbit = parm[0]; // store the first bit as sigbit, used when
								// signal sets.
		int len = parm.length;
		int count = 0;
		int res = 0;

		/***
		 * the following table shows the relationships between the sigbit and
		 * signal, and the result.
		 * 
		 * signal set | result ||signal not set | result
		 * -------------+---------------++------------------+-----------
		 * 1XXXXXXX | negative ||1XXXXXXX | positive
		 * -------------+---------------++------------------+-----------
		 * 0XXXXXXX | positive ||0XXXXXXX | positive
		 * 
		 * So, negative number only appear when signal bit set and sigbit equals
		 * one.
		 */
		if (signal && sigbit == 1 && (len % 8 == 0)) {

			res = -1;
			for (int i = 0; i < len; ++i, ++count) {
				// first step, get the complimentary of the rest part of input
				// array:
				int tmp = 1 - parm[i];
				// then calculate the integer result of it.
				res -= Math.pow(2, len - count - 1) * tmp;
			}
		} else {
			for (int i = 0; i < len; ++i, ++count) {
				res += Math.pow(2, len - count - 1) * parm[i];
			}

		}

		return res;
	}

	/**
	 * This function accept the source array as the first parameter, the start
	 * bit as the second parameter and the end bit as the third parameter. The
	 * return of this function will be an array, which length is endbit -
	 * startbit + 1. The first bit of the returned array is the bit which index
	 * is startbit of the source array, and the last bit of the returned array
	 * is the bit of which index is the end bit of the source array.
	 * 
	 * 
	 * For example, the source array is: int[] a = {10, 20, 30, 40, 50, 60}, and
	 * the start bit is set to 2 and the end bit is set to 5. then let int[] b =
	 * Util.subArray(a, 2, 5). Thus, the length of array b should be (5-2)+1=3,
	 * the first element in array b is the element in array a with index 2,
	 * which is 30, and the last element in array b is the element in array a
	 * with index 5, which is 60. So the return array would be: {30, 40, 50, 60}
	 * 
	 * @param parm
	 * @param startBit
	 * @param endBit
	 * @return parm[startBit, engBit]
	 * @throws SubArrayException
	 */
	public static int[] subArray(int[] parm, int startBit, int endBit)
			throws SubArrayException {
		
		int len = endBit - startBit;
		int[] ret = new int[len + 1];

		// possible exceptions:
		if (len < 0) {
			throw new SubArrayException("in SubArray(): start bit(" + startBit
					+ ") should smaller than end bit(" + endBit + ")!");
		}

		if (len > parm.length) {
			throw new SubArrayException("length error: " + " end bit(" + endBit
					+ ") - start bit(" + startBit + ") = " + len
					+ ", which is bigger than the length "
					+ "of parm[]'s length(" + parm.length + ")");
		}

		try {
			for (int i = 0; i < len + 1; i++)
				ret[i] = parm[startBit + i];
		} catch (ArrayIndexOutOfBoundsException e) {
			throw new SubArrayException("startBit (" + startBit
					+ ") less than 0 or end bit (" + endBit + ") exceeded the "
					+ "size (" + (parm.length - 1) + ") of source array.");
		}
		return ret;
	}

	/***
	 * Use to trim the length of binary operation. The function is as following:
	 * 
	 * if (len <= 8) return 8;
	 * 
	 * else if (len <= 16) return 16;
	 * 
	 * else if (len <= 32) return 32;
	 * 
	 * else if (len <= 64) return 64;
	 * 
	 * else return 128;
	 * 
	 * @param len
	 * @return
	 */
	public static int trimLength(int len) {
		if (len <= 8)
			return 8;
		else if (len <= 16)
			return 16;
		else if (len <= 32)
			return 32;
		else if (len <= 64)
			return 64;
		else
			return 128;
	}

	/***
	 * trim an array, remove extra 0s. for example, trimArray({0,0,0,1,0})
	 * return {1,0}.
	 * 
	 * @param op
	 * @return trimed array
	 */
	public static int[] trimArray(int[] op) {
		// ptr is a pointer to go thru the input array, from left to right.
		// if op[ptr] == 0, then ptr points to the next number in the array;
		// if not, then ptr pointed to the left-most 1 in the array, then
		// this is the first element in the result array.

		int ptr = 0;
		for (ptr = 0; ptr < op.length; ptr++) {
			if (op[ptr] != 0)
				break;
		}

		int[] ret = new int[op.length - ptr];

		for (int i = 0; i < ret.length; ++i, ptr++) {
			ret[i] = op[ptr];
		}

		return ret;
	}
	
}
